package com.takeshi.landlords.config;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.takeshi.landlords.entity.ChannelGroupObj;
import com.takeshi.landlords.entity.GamePlayer;
import com.takeshi.landlords.entity.PlayHistoryObj;
import com.takeshi.landlords.resp.Command;
import com.takeshi.landlords.resp.SysConstant;
import com.takeshi.landlords.resp.TakeshiException;
import com.takeshi.landlords.util.CardModel;
import com.takeshi.landlords.util.DoubleLinkedList;
import com.takeshi.landlords.util.UserState;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

/**
 * NettyWebSocketHandler
 *
 * @author Nanase Takeshi
 * @date 2022/2/16 14:32
 */
@Slf4j
public class NettyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    /**
     * 初始房间号
     */
    public static AtomicInteger initialRoomNumber = new AtomicInteger(1000);

    /**
     * 房间号与房间通道组的信息
     * key：房间号
     */
    public static Map<Integer, ChannelGroupObj> groupObjMap = new ConcurrentHashMap<>();

    /**
     * 通道ID与用户的map信息
     * key：channelId
     */
    public static Map<ChannelId, GamePlayer> userGroupRepository = new ConcurrentHashMap<>();

    /**
     * 事件回调
     *
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
            GamePlayer gamePlayer = userGroupRepository.get(ctx.channel().id());
            //60秒读空闲关闭通道
            log.info("HeartBeatHandler.userEventTriggered --> 用户：{} 读空闲", gamePlayer.getNickname());
            ctx.close();
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        String content = msg.text().trim();
        if (StrUtil.isBlank(content)) {
            ctx.writeAndFlush(new TextWebSocketFrame());
            return;
        }
        if (content.length() > SysConstant.MAX_MSG_LENGTH) {
            throw new TakeshiException("The message content is too long, please enter a message within 50 characters");
        }
        Channel channel = ctx.channel();
        ChannelId channelId = channel.id();
        GamePlayer gamePlayer = userGroupRepository.get(channelId);
        if (ObjectUtil.isNull(gamePlayer)) {
            //设置名字
            userGroupRepository.put(channelId, new GamePlayer().setChannelId(channel.id()).setChannelId(channelId).setNickname(content).setState(UserState.BASE_CHOOSE.getState()));
            throw new TakeshiException(StrUtil.format(SysConstant.WELCOME + "<br/>" + SysConstant.SELECT_TEXT, content));
        }
        if (gamePlayer.getState() == UserState.BASE_CHOOSE.getState() && StrUtil.equalsAny(content, SysConstant.SELECT_SCOPE)) {
            //选择[创建房间/加入房间]界面
            this.operationInTheSelect(channel, gamePlayer, content);
        } else if (gamePlayer.getState() == UserState.JOIN_ROOM.getState()) {
            //加入房间选择房间号界面
            this.selectRoomNumber(channel, gamePlayer, content);
        } else if (gamePlayer.getState() == UserState.ENTER_PWD.getState()) {
            //输入房间密码界面
            this.enterPwd(channel, gamePlayer, content);
        } else if (UserState.inTheRoom(gamePlayer.getState())) {
            //房间中界面
            this.operationInTheRoom(channel, gamePlayer, content);
        } else {
            ctx.writeAndFlush(new TextWebSocketFrame(msg.text()));
        }
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        log.info("NettyWebSocketHandler.handlerAdded --> channelId: {}", ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
        log.info("NettyWebSocketHandler.handlerRemoved --> channelId: {}", ctx.channel().id().asLongText());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        log.info("NettyWebSocketHandler.channelActive -> channelId: {}", ctx.channel().id().asLongText());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
        GamePlayer gamePlayer = userGroupRepository.get(ctx.channel().id());
        if (ObjectUtil.isNotNull(gamePlayer) && ObjectUtil.isNotNull(gamePlayer.getRoomNumber())) {
            ChannelGroupObj channelGroupObj = groupObjMap.get(gamePlayer.getRoomNumber());
            if (ObjectUtil.isNull(channelGroupObj) || CollUtil.isEmpty(channelGroupObj.getChannelGroup())) {
                //解散房间
                groupObjMap.remove(gamePlayer.getRoomNumber());
            }
        }
        log.info("NettyWebSocketHandler.channelInactive -> channelId: {}，用户：{}", ctx.channel().id().asLongText(), gamePlayer.getNickname());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof TakeshiException) {
            ctx.writeAndFlush(new TextWebSocketFrame(cause.getMessage()));
        } else {
            cause.printStackTrace();
            log.error("NettyWebSocketHandler.exceptionCaught --> cause: ", cause);
            ctx.close();
        }
    }

    /**
     * 创建房间
     *
     * @param channel
     * @param gamePlayer
     */
    private synchronized void createRoom(Channel channel, GamePlayer gamePlayer) throws TakeshiException {
        //生成一个房间号
        int roomNumber = initialRoomNumber.getAndIncrement();
        ChannelGroupObj channelGroupObj = new ChannelGroupObj();
        channelGroupObj.setRoomNumber(roomNumber);
        channelGroupObj.setRunning(false);
        channelGroupObj.setHomeownerChannelId(channel.id());

        ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
        channelGroup.add(channel);
        channelGroupObj.setChannelGroup(channelGroup);

        gamePlayer.setRoomNumber(roomNumber).setState(UserState.WAITING_FOR_GAME_START.getState());
        DoubleLinkedList<GamePlayer> doubleLinkedList = new DoubleLinkedList<>();
        doubleLinkedList.add(gamePlayer);
        channelGroupObj.setUserList(doubleLinkedList);

        groupObjMap.put(roomNumber, channelGroupObj);
        throw new TakeshiException(StrUtil.format("Create room successfully，id：{}", roomNumber));
    }

    /**
     * 加入房间
     *
     * @param channel
     * @param gamePlayer
     * @param roomNumber 要加入的房间号
     * @throws TakeshiException
     */
    private synchronized void joinRoom(Channel channel, GamePlayer gamePlayer, int roomNumber) throws TakeshiException {
        ChannelGroupObj channelGroupObj = groupObjMap.get(roomNumber);
        if (StrUtil.isNotBlank(channelGroupObj.getPwd())) {
            //房间设置了密码
            gamePlayer.setState(UserState.ENTER_PWD.getState());
            throw new TakeshiException("Please enter the 4-digit room code...");
        }
        this.joinRoomAgain(channel, gamePlayer, roomNumber, channelGroupObj);
    }

    /**
     * 格式化房间表格信息
     *
     * @return
     */
    private String formattingTable() {
        String collect = groupObjMap.entrySet().stream().map(item ->
                item.getKey() + SysConstant.NBSP + (item.getValue().getRunning() ? SysConstant.RUNNING : SysConstant.WAITING) + SysConstant.NBSP + "&nbsp;&nbsp;&nbsp;" + item.getValue().getChannelGroup().size()
        ).collect(Collectors.joining("<br/>"));
        return SysConstant.TABLE_HEADER + "<br/>" + StrUtil.blankToDefault(collect, SysConstant.NBSP + "&nbsp;&nbsp;no data");
    }

    /**
     * 在选择[创建房间/加入房间界面]的指令操作
     *
     * @param channel
     * @param gamePlayer
     * @param content
     */
    private void operationInTheSelect(Channel channel, GamePlayer gamePlayer, String content) throws TakeshiException {
        if (StrUtil.equals(content, "1")) {
            //创建房间
            this.createRoom(channel, gamePlayer);
        } else {
            //返回可选择的房间表格信息
            gamePlayer.setState(UserState.JOIN_ROOM.getState());
            throw new TakeshiException(this.formattingTable());
        }
    }

    /**
     * 选择加入房间的选择房间号界面中的加入房间操作
     *
     * @param channel
     * @param gamePlayer
     * @param content
     * @throws TakeshiException
     */
    private void selectRoomNumber(Channel channel, GamePlayer gamePlayer, String content) throws TakeshiException {
        if (StrUtil.equalsIgnoreCase(content, Command.REFRESH.getCommand())) {
            //查看最新的可用房间列表
            throw new TakeshiException(this.formattingTable());
        }
        if (StrUtil.equalsIgnoreCase(content, Command.EXIT.getCommand())) {
            //返回上一级
            gamePlayer.setState(UserState.BASE_CHOOSE.getState());
            throw new TakeshiException(SysConstant.SELECT_TEXT);
        }
        if (groupObjMap.keySet().stream().anyMatch(item -> StrUtil.equals(item.toString(), content))) {
            //输入的是房间号
            this.joinRoom(channel, gamePlayer, Integer.parseInt(content));
        } else {
            throw new TakeshiException("<br/>Room number invalid.<br/>" + this.formattingTable());
        }
    }

    /**
     * 输入房间密码操作
     *
     * @param channel
     * @param gamePlayer
     * @param content
     * @throws TakeshiException
     */
    private void enterPwd(Channel channel, GamePlayer gamePlayer, String content) throws TakeshiException {
        Integer roomNumber = gamePlayer.getRoomNumber();
        if (StrUtil.equalsIgnoreCase(content, Command.EXIT.getCommand())) {
            //退出输入密码界面返回基础选择界面
            gamePlayer.setState(UserState.BASE_CHOOSE.getState());
            throw new TakeshiException(SysConstant.SELECT_TEXT);
        }
        ChannelGroupObj channelGroupObj = groupObjMap.get(roomNumber);
        if (!StrUtil.equals(channelGroupObj.getPwd(), content)) {
            throw new TakeshiException("Password entered incorrectly, please try again...");
        }
        this.joinRoomAgain(channel, gamePlayer, roomNumber, channelGroupObj);
    }

    /**
     * 加入房间的重复代码
     *
     * @param channel
     * @param gamePlayer
     * @param roomNumber
     * @param channelGroupObj
     * @throws TakeshiException
     */
    private void joinRoomAgain(Channel channel, GamePlayer gamePlayer, Integer roomNumber, ChannelGroupObj channelGroupObj) throws TakeshiException {
        gamePlayer.setState(UserState.BASE_CHOOSE.getState());
        if (ObjectUtil.isNull(channelGroupObj) || CollUtil.isEmpty(channelGroupObj.getChannelGroup())) {
            throw new TakeshiException("Failed to join, the room has been disbanded...<br/>" + SysConstant.SELECT_TEXT);
        }
        if (channelGroupObj.getRunning()) {
            throw new TakeshiException("Failed to join, the room has started the game...<br/>" + SysConstant.SELECT_TEXT);
        }
        if (channelGroupObj.getChannelGroup().size() == SysConstant.MAX_NUMBER_OF_PEOPLE) {
            throw new TakeshiException("Failed to join, the room is full...<br/>" + SysConstant.SELECT_TEXT);
        }
        channelGroupObj.getChannelGroup().add(channel);
        gamePlayer.setRoomNumber(roomNumber).setState(UserState.WAITING_FOR_GAME_START.getState());
        channelGroupObj.getUserList().add(gamePlayer);
        channelGroupObj.getChannelGroup().writeAndFlush(new TextWebSocketFrame(StrUtil.format("Player {} join a room, there are {} players in the current room {}", gamePlayer.getNickname(), channelGroupObj.getChannelGroup().size(), SysConstant.ROOM_COMMAND_TIPS)));
    }

    /**
     * 在房间中的指令操作
     *
     * @param channel
     * @param gamePlayer
     * @param content
     */
    private void operationInTheRoom(Channel channel, GamePlayer gamePlayer, String content) throws TakeshiException {
        Integer roomNumber = gamePlayer.getRoomNumber();
        ChannelGroupObj channelGroupObj = groupObjMap.get(roomNumber);
        if (StrUtil.equalsIgnoreCase(content, Command.EXIT.getCommand())) {
            //退出房间
            channelGroupObj.exitRoom(gamePlayer, (channelGroup) -> {
                if (channelGroup.size() == 0) {
                    //解散房间
                    groupObjMap.remove(roomNumber);
                }
                channel.writeAndFlush(new TextWebSocketFrame(SysConstant.SELECT_TEXT));
            });
            return;
        }
        if (StrUtil.equalsIgnoreCase(content, Command.REFRESH.getCommand())) {
            //查看示例和房间成员
            String collect =
                    channelGroupObj.getUserList()
                            .toList()
                            .stream()
                            .map(item -> {
                                String state = Optional.ofNullable(channelGroupObj.getChannelGroup().find(item.getChannelId())).map(Channel::isActive).orElse(false) ? "online" : "dropped";
                                return StrUtil.BRACKET_START + item.getNickname() + StrUtil.SPACE + StrUtil.DASHED + StrUtil.SPACE + state + StrUtil.BRACKET_END;
                            })
                            .collect(Collectors.joining(StrUtil.SPACE));
            throw new TakeshiException(SysConstant.EXAMPLE + "<br/>room members: " + collect);
        }
        if (StrUtil.equalsIgnoreCase(content, Command.VIEW.getCommand())) {
            //查看前几个人出的牌
            int size = CollUtil.size(channelGroupObj.getPlayHistory());
            String join = CollUtil.isEmpty(channelGroupObj.getPlayHistory())
                    ? "暂无出牌记录"
                    : channelGroupObj.getPlayHistory().stream().skip(size - Math.min(size, 2)).map(PlayHistoryObj::showPlayHistory).collect(Collectors.joining("<br/>"));
            String handCard = CollUtil.isEmpty(gamePlayer.getHandCardList()) ? "" : String.join(StrUtil.SPACE, gamePlayer.getHandCardList());
            channel.writeAndFlush(new TextWebSocketFrame(StrUtil.format("<br/>最后{}次的出牌历史记录:<br/>{}<br/>[{}] your hand cards is [{}]", channelGroupObj.getUserList().size() - 1, join, gamePlayer.getNickname(), handCard)));
            return;
        }
        if (channelGroupObj.getRunning() && channelGroupObj.getCurrentPlayerChannelId().compareTo(gamePlayer.getChannelId()) == 0) {
            if (gamePlayer.getState() == UserState.CHOOSE_LANDLORD.getState()) {
                //选择地主
                this.chooseLandlord(channelGroupObj, gamePlayer, content);
                return;
            }
            List<String> list = Dealer.toList(content);
            String sortContent = String.join(StrUtil.EMPTY, list);
            //检查牌型
            CardModel cardModel = this.checkCardModel(sortContent);
            if (ObjectUtil.isNotNull(cardModel)
                    && StrUtil.equalsIgnoreCase(Command.PASS.getCommand(), content)
                    && CollUtil.isNotEmpty(channelGroupObj.getPlayHistory())
                    && CollUtil.reverseNew(channelGroupObj.getPlayHistory()).stream().limit(channelGroupObj.getUserList().size() - 1).anyMatch(item -> !item.getCardModel().equals(CardModel.PASS))) {
                //不出牌
                channelGroupObj.passCard(gamePlayer);
                return;
            }
            if (ObjectUtil.isNotNull(cardModel) && ReUtil.isMatch(CardModel.ALL_CARD.getPattern(), content)) {
                //出牌
                channelGroupObj.playCards(cardModel, gamePlayer, list, content);
                return;
            }
            channelGroupObj.getChannelGroup().writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] say: {}", gamePlayer.getNickname(), content)));
        } else if (channelGroupObj.getHomeownerChannelId().compareTo(gamePlayer.getChannelId()) == 0) {
            if (StrUtil.equalsIgnoreCase(Command.START_GAME.getCommand(), content) && channelGroupObj.getChannelGroup().size() > 1) {
                //开始游戏，发牌
                Dealer.deal(channelGroupObj);
                Channel ch = channelGroupObj.getChannelGroup().stream().findAny().orElseThrow(TakeshiException::new);
                channelGroupObj.setRunning(true).setFirstLandGrabberChannelId(ch.id()).setCurrentPlayerChannelId(ch.id());
                //通知用户叫地主
                ch.writeAndFlush(new TextWebSocketFrame(StrUtil.format(SysConstant.TIMEOUT_TEXT, SysConstant.COUNTDOWN, SysConstant.TO_BECOME_LANDLORD)));
                List<String> list = new ArrayList<>();
                channelGroupObj.getUserList().foreach(item -> list.add(item.getNickname()));
                log.info("NettyWebSocketHandler.operationInTheRoom --> roomNumber：{}，list：{}", roomNumber, JSONUtil.toJsonStr(list));
                return;
            }
            if (StrUtil.startWithIgnoreCase(content, Command.SET_PWD.getCommand())) {
                String after = StrUtil.subAfter(content, Command.SET_PWD.getCommand(), false);
                if (after.length() != SysConstant.PWD_LENGTH) {
                    throw new TakeshiException("Please set a 4-digit room password");
                }
                channelGroupObj.setPwd(after);
                throw new TakeshiException(StrUtil.format("[{}] room, password set successfully：{}", roomNumber, after));
            }
            if (StrUtil.equalsIgnoreCase(Command.CLOSE_OFF.getCommand(), content)) {
                channelGroupObj.setPwd(null);
                throw new TakeshiException(StrUtil.format("[{}] room, password cancelled successfully", roomNumber));
            }
            channelGroupObj.getChannelGroup().writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] say: {}", gamePlayer.getNickname(), content)));
        } else {
            //不是房间内指令则给房间内好友发消息，也回显给自己
            channelGroupObj.getChannelGroup().writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] say: {}", gamePlayer.getNickname(), content)));
        }
    }

    /**
     * 选择地主
     *
     * @param channelGroupObj
     * @param gamePlayer
     * @param content
     */
    private void chooseLandlord(ChannelGroupObj channelGroupObj, GamePlayer gamePlayer, String content) {
        if (StrUtil.equalsIgnoreCase("y", content) || StrUtil.equalsIgnoreCase("yes", content)) {
            //用户选择成为地主，将底牌放入地主手牌中
            gamePlayer.becomeLandlord(channelGroupObj.getHoleCard());
            channelGroupObj.getUserList().foreach(item -> item.setState(UserState.IN_COMPETITION.getState()));
            channelGroupObj.setLandlordNickname(gamePlayer.getNickname()).setCurrentPlayerChannelId(gamePlayer.getChannelId());
            channelGroupObj.getChannelGroup().writeAndFlush(new TextWebSocketFrame(StrUtil.format("[{}] Become a landlord, hole card is [{}], the landlord please play your cards...", gamePlayer.getNickname(), String.join(StrUtil.SPACE, channelGroupObj.getHoleCard()))));
            Optional.ofNullable(channelGroupObj.getChannelGroup().find(gamePlayer.getChannelId())).map(item -> item.writeAndFlush(new TextWebSocketFrame(StrUtil.format(SysConstant.TIMEOUT_TEXT + "[{}] hand cards is [{}]", SysConstant.COUNTDOWN, StrUtil.COMMA, gamePlayer.getNickname(), String.join(StrUtil.SPACE, gamePlayer.getHandCardList())))));
        }
        if (StrUtil.equalsIgnoreCase("n", content) || StrUtil.equalsIgnoreCase("no", content)) {
            //用户放弃成为地主
            channelGroupObj.giveUpBeingLandlord(gamePlayer);
        }
    }

    /**
     * 检查牌型
     *
     * @param content
     * @return
     */
    private CardModel checkCardModel(String content) {
        if (StrUtil.equalsIgnoreCase(Command.PASS.getCommand(), content)) {
            return CardModel.PASS;
        }
        if (content.length() == 1 && ReUtil.isMatch(CardModel.LEAFLET.getPattern(), content)) {
            //单张
            return CardModel.LEAFLET;
        }
        if (content.length() == 2 && ReUtil.isMatch(CardModel.PAIR.getPattern(), content)) {
            //对子
            return CardModel.PAIR;
        }
        if (content.length() == 2 && ReUtil.isMatch(CardModel.KING_FRIED.getPattern(), content)) {
            //王炸
            return CardModel.KING_FRIED;
        }
        if (content.length() == 3 && ReUtil.isMatch(CardModel.THREE_IDENTICAL_CARDS.getPattern(), content)) {
            //三张相同的牌（三不带）
            return CardModel.THREE_IDENTICAL_CARDS;
        }
        if (content.length() == 4 && ReUtil.isMatch(CardModel.BOMB.getPattern(), content)) {
            //四个的炸弹
            return CardModel.BOMB;
        }
        if (content.length() == 4 && ReUtil.isMatch(CardModel.THREE_BELT_ONE.getPattern(), content)) {
            //三带一
            return CardModel.THREE_BELT_ONE;
        }
        if (content.length() == 5 && ReUtil.isMatch(CardModel.THREE_BELT_PAIR.getPattern(), content)) {
            //三带一对
            return CardModel.THREE_BELT_PAIR;
        }
        if (content.length() >= 5) {
            if (Dealer.STRAIGHT_STR.contains(content)) {
                //顺子
                return CardModel.STRAIGHT_PATTERN;
            }
            if (content.length() % 2 == 0 && Dealer.PAIR_STR.contains(content)) {
                //连对
                return CardModel.PAIR_PATTERN;
            }
            List<String> airplaneList = ReUtil.findAllGroup0(CardModel.AIRPLANE_PATTERN.getPattern(), content);
            String str = String.join(StrUtil.EMPTY, airplaneList);
            if (Dealer.AIRPLANE_STR.contains(str)) {
                //飞机带的牌
                String airplaneContent = ReUtil.delAll(CardModel.AIRPLANE_PATTERN.getPattern(), content);
                if (StrUtil.isBlank(airplaneContent)) {
                    //飞机不带翅膀
                    return CardModel.AIRPLANE_PATTERN;
                }
                if (airplaneContent.length() == airplaneList.size()) {
                    //飞机带翅膀（带单张）
                    return CardModel.AIRCRAFT_WITH_SINGLE_CARD_PATTERN;
                }
                if (ReUtil.findAllGroup0(CardModel.PAIR.getPattern(), airplaneContent).size() == airplaneList.size()) {
                    //飞机带翅膀（带对子）
                    return CardModel.AIRPLANE_WITH_PAIR_PATTERN;
                }
            }
            if (ReUtil.isMatch(CardModel.FOUR_WITH_TWO.getPattern(), content)) {
                //四带两张
                return CardModel.FOUR_WITH_TWO;
            }
            if (ReUtil.isMatch(CardModel.FOUR_WITH_TWO_PAIRS.getPattern(), content)) {
                //四带两对
                return CardModel.FOUR_WITH_TWO_PAIRS;
            }
        }
        return null;
    }

}
